//
//  XJSqlite3Tool.swift
//  XJSqlite3Tool
//
//  Created by 李胜兵 on 2017/6/10.
//  Copyright © 2017年 善林(中国)金融信息服务有限公司. All rights reserved.
//
/** SQlite3语句工具类所有操作:
 
 1:func xj_openDB => 打开数据库
 2:func xj_closeDB => 关闭数据库
 3:func xj_deal => 处理sql语句（增、删、改）
 4:func xj_querySql => 处理查询sql语句(查询)
 5:func xj_dealSqls => 通过事物处理多个语句并发操作
 */



import UIKit
//private let kCachePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
private let kCachePath = "/Users/lishengbing/Desktop"
private let kCommomDB = "commom"



//============================================================
//       Check 1 - 创建单例类
//============================================================
class XJSqlite3Tool: NSObject {
    static let share: XJSqlite3Tool = XJSqlite3Tool()
    fileprivate var db: OpaquePointer? = nil
}



//============================================================
//       Check 1 - 打开和关闭数据库
//============================================================
/**
 打开和关闭数据库
 
 @param uid 用户id
 @return 是否成功
 */
extension XJSqlite3Tool {
   fileprivate func xj_openDB(uid: String) -> Bool {
        var dbName = "/\(uid).sqlite"
        if uid.characters.count == 0 {
            dbName = "/\(kCommomDB).sqlite"
        }
        let dbPath = kCachePath + dbName
        return sqlite3_open(dbPath, &db) == SQLITE_OK
    }
   fileprivate func xj_closeDB() {
        sqlite3_close(db)
    }
}



//============================================================
//       Check 2 - 数据库操作(增、删、改)
//============================================================
/**
 数据库操作(增、删、改)
 
 @param sql 操作语句
 @param uid 用户id
 @return 是否成功
 */
extension XJSqlite3Tool {

    func xj_deal(sql: String, uid: String) -> Bool {
        
        // 1:打开数据库
        if !self.xj_openDB(uid: uid) {
            print("增删改：=========打开数据库失败")
            return false
        }
        
        // 2:执行语句
        let result = sqlite3_exec(db, sql, nil, nil, nil) == SQLITE_OK
        
        // 3:关闭数据库
        self.xj_closeDB()
        
        // 4:返回result结果
        return result
        
    }
}



//============================================================
//       Check 3 - 查询操作
//============================================================
/**
 查询操作
 
 @param sql 操作语句
 @param uid 用户id
 @return 是否成功
 */
extension XJSqlite3Tool {
    // 返回值：字典(一行记录)组成的数组
    func xj_querySql(sql: String, uid: String) -> [[String : Any]] {
        
        // 1:校验打开数据库
        if !self.xj_openDB(uid: uid) {
            print("查询:============打开数据库失败")
            return [[:]]
        }
        /* 方案一：
         这个函数最大的不好就是它将所有的字段都以字符串返回给我们、不存在什么double等字段；所以我们使用准备语句进行查询
         sqlite3_exec(<#T##OpaquePointer!#>, <#T##sql: UnsafePointer<Int8>!##UnsafePointer<Int8>!#>, <#T##callback: ((UnsafeMutableRawPointer?, Int32, UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?, UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?) -> Int32)!##((UnsafeMutableRawPointer?, Int32, UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?, UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?) -> Int32)!##(UnsafeMutableRawPointer?, Int32, UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?, UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?) -> Int32#>, <#T##UnsafeMutableRawPointer!#>, <#T##errmsg: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>!##UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>!#>)
         */
        // 方案二：
        // 2.准备语句（预处理语句）
        /* 准备语句不是只可以查询、它可以做很多事情、上面的操作语句其实也是可以使用准备语句进行操作、
         比如说插入一条记录：如果使用上面的操作语句必须将sql语句写全
         let sql = "insert into t_stu(name, age) values('abc', 28)"
         如果需要插入一条data数据如0x0000000E、上面那样方法操作肯定是不行、所以这时候只能使用准备语句进行插入
         let sql = "insert into t_stu(name, age) values('0x0000000E', 28)"
         let sql = "insert into t_stu(name, age) values(？, ？)"
         所以准备语句可以这样理解：表示已开始处理这个语法、如果语法没有问题表示语句格式是正确的、这时候只需要将需要插入的数据
         塞到那个相应位置即可（即绑定数据）-> （然后执行）-> （执行完以后再重置当前数据、再此插入新数据）-> （释放资源：数据）
         
         1: 创建准备语句
         2: 绑定数据
         3: 执行
         4: 重置
         5: 释放资源
         */
        
        // 01-创建准备语句
        // <#T##db: OpaquePointer!##OpaquePointer!#>:一个打开的数据库
        // <#T##zSql: UnsafePointer<Int8>!##UnsafePointer<Int8>!#>: 需要执行的sql语句
        // <#T##nByte: Int32##Int32#>:参数2取出多少字节的长度 -1表示自动计算 找到\0自动结束
        // <#T##ppStmt: UnsafeMutablePointer<OpaquePointer?>!##UnsafeMutablePointer<OpaquePointer?>!#>: 准备语句
        // <#T##pzTail: UnsafeMutablePointer<UnsafePointer<Int8>?>!##UnsafeMutablePointer<UnsafePointer<Int8>?>!#>: 通过参数3，取出参数2的长度字节后，剩余的字符串
        
        var ppStmt: OpaquePointer? = nil
        if (sqlite3_prepare_v2(db, sql, -1, &ppStmt, nil) != SQLITE_OK) {
            print("准备语句编译失败")
            return [[:]]
        }
        
        // 3-绑定数据（可省略）
        
        
        // 4-执行
        var rowDictArray: [[String : Any]] = [[String : Any]]()
        while sqlite3_step(ppStmt) == SQLITE_ROW {
            // 这里面就是一行记录
            
            // 1: 获取所有列的个数
            let columnCount = sqlite3_column_count(ppStmt)
            
            
            var rowDict: [String : Any] = [String : Any]()
            // 2: 遍历所有的列
            for i in 0..<columnCount {
                // 2.1 获取列名
                let columnName = String(cString: sqlite3_column_name(ppStmt, i), encoding: String.Encoding.utf8)
                
                // 2.2 获取列值
                // 不同列的类型、使用不同的函数、进行获取
                // 2.2.1: 获取列的类型
                let type = sqlite3_column_type(ppStmt, i)
                
                // 2.2.2: 根据列的类型。使用不同的函数，进行获取
                var value: String = ""
                switch type {
                case SQLITE_INTEGER:
                    value = "\(sqlite3_column_int(ppStmt, i))"
                    
                case SQLITE_FLOAT:
                    value = "\(sqlite3_column_double(ppStmt, i))"
                    
                case SQLITE_BLOB:
                    value = "\(sqlite3_column_blob(ppStmt, i))"
                    
                case SQLITE_NULL:
                    value = ""
                    
                case SQLITE3_TEXT:
                    let cValue = sqlite3_column_text(ppStmt, i)!
                    value = String(cString: cValue)
                    
                default:
                    break
                }
                rowDict[columnName!] = value
            }
            rowDictArray.append(rowDict)
        }
        
        // 5-重置（可省略）
        
        // 6-释放资源
        sqlite3_finalize(ppStmt)
        
        // 7-关闭数据库
        self.xj_closeDB()
        
        // 8-返回查询结果字典数组
        return rowDictArray
    }
}



//============================================================
//       Check 4 - 多条语句事务操作(不包括查询)
//============================================================
/**
 多条语句事务操作(不包括查询)
 
 @param sql 操作语句
 @param uid 用户id
 @return 是否成功
 */
extension XJSqlite3Tool {
    func xj_dealSqls(sqls: [String], uid: String) -> Bool {
        
        // 开始事物
        self.beginTransaction(uid: uid)
        // 执行
        for sql in sqls {
           let result = self.xj_deal(sql: sql, uid: uid)
            if result == false {
                self.rollbackTransaction(uid: uid)
                return false 
            }
        }
        
        // 提交事物
        self.commitTransaction(uid: uid)
        return true
    }
    
    // 开始事物
    func beginTransaction(uid: String) {
       _ = self.xj_deal(sql: "begin transaction", uid: uid)
    }
    
    // 提交事物
    func commitTransaction(uid: String) {
       _ = self.xj_deal(sql: "commit transaction", uid: uid)
    }
    
    // 回滚事物
    func rollbackTransaction(uid: String) {
       _ = self.xj_deal(sql: "rollback transaction", uid: uid)
    }
}












